package parser;



import scanner.LexicalException;
import scanner.Scanner;
import scanner.Token;
import util.AST.AST;

/**
 * Parser class
 * @version 2010-august-29
 * @discipline Projeto de Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Parser {

	// The current token
	private Token currentToken = null;
	// The scanner
	private Scanner scanner = null;
	
	/**
	 * Parser constructor
	 */
	public Parser() {
		// Initializes the scanner object
		this.scanner = new Scanner();
	}
	
	public void parseGeral() throws SyntacticException, LexicalException{
		parsePrograma();
		accept(GrammarSymbols.EOT);
	}
	
	private void parsePrograma() throws SyntacticException, LexicalException{
		
		if(this.currentToken.getKind()!=GrammarSymbols.EOT){
			
		accept(GrammarSymbols.PROGRAMA);
		accept(GrammarSymbols.ID);
		
		while(currentToken.getKind() != GrammarSymbols.PROGRAMA){
			parseComando();
		}
		accept(GrammarSymbols.END);
		accept(GrammarSymbols.PROGRAMA);
		}
	}
	
	private void parseComandoSimples()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.TIPO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.TIPO_LOGICO
				||this.currentToken.getKind()==GrammarSymbols.TIPO_REAL){
			parseDeclaracaoVariavel();
		}
		else if(this.currentToken.getKind()==GrammarSymbols.DO){
			acceptIt();
			parseComandoWhile();
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.DO);
		}
		
		else if(this.currentToken.getKind()==GrammarSymbols.IF){
			
			acceptIt();
			parseExpressao();
			accept(GrammarSymbols.THEN);
			
			while(this.currentToken.getKind()!=GrammarSymbols.ELSE||this.currentToken.getKind()!=GrammarSymbols.END){
				
				parseComandoSimples();
			}
			
			if(this.currentToken.getKind()==GrammarSymbols.ELSE){
				acceptIt();
				while(this.currentToken.getKind()!=GrammarSymbols.END){
					parseComandoSimples();
				}
			}
			
			
			accept(GrammarSymbols.END);
			accept(GrammarSymbols.IF);
			
		}
		
		else if(this.currentToken.getKind()==GrammarSymbols.CALL){
			parseChamadaSubrotina();
		}
		
		else if(this.currentToken.getKind()==GrammarSymbols.PRINT){
			acceptIt();
			accept(GrammarSymbols.MULTIPLICACAO);
			accept(GrammarSymbols.VIRGULA);
			
			if(this.currentToken.getKind()==GrammarSymbols.EOL){
				acceptIt();
			}
			else if(this.currentToken.getKind()==GrammarSymbols.ASPAS){
				acceptIt();
				while(this.currentToken.getKind()!=GrammarSymbols.ASPAS){
					
					acceptIt();
				}
				acceptIt();
				
				while(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
					acceptIt();
					parseExpressao();
				}
			}
			else  if(this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.VALOR_LOGICO
				||this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL){
					acceptIt();
					while(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
						acceptIt();
						parseExpressao();
				}
			}
			else if(this.currentToken.getKind()==GrammarSymbols.ID){
				acceptIt();
				if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
					acceptIt();
					parseParametros();
					accept(GrammarSymbols.PARENTESE_DIR);
				}
			}
			
		}
		else parseAtribuicaoVariavel(); 
	}

	private void parseDeclaracaoVariavel()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.TIPO_INTEIRO
				||this.currentToken.getKind()==GrammarSymbols.TIPO_REAL
				||this.currentToken.getKind()==GrammarSymbols.TIPO_LOGICO){
			
			acceptIt();
			accept(GrammarSymbols.DECLARACAO);
			accept(GrammarSymbols.ID);
			
			while(this.currentToken.getKind()!=GrammarSymbols.EOL){
				
			if(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
				acceptIt();
				
				if(this.currentToken.getKind()==GrammarSymbols.EOL){
					
					throw new SyntacticException("Erro sintatico", this.currentToken);
				}
				else if(this.currentToken.getKind()==GrammarSymbols.ID){
					acceptIt();
				}
				else parseExpressao();		
			}
			
			else {
				
				accept(GrammarSymbols.ATRIBUICAO);
				if(this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL
						||this.currentToken.getKind()==GrammarSymbols.VERDADEIRO||this.currentToken.getKind()==GrammarSymbols.FALSO){
					acceptIt();
				}
				else {
					parseExpressao();
				}
				
			}
			}
		}
	}
	
	private void parseAtribuicaoVariavel()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.ID)
			acceptIt();
			accept(GrammarSymbols.ATRIBUICAO);
			
			if(this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO||this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL
					|| this.currentToken.getKind()==GrammarSymbols.VERDADEIRO ||this.currentToken.getKind()==GrammarSymbols.FALSO){
				
			while(this.currentToken.getKind()!=GrammarSymbols.EOL){
				acceptIt();
				if(this.currentToken.getKind()!=GrammarSymbols.EOL){
					if(this.currentToken.getKind()==GrammarSymbols.SOMA||this.currentToken.getKind()==GrammarSymbols.MULTIPLICACAO
							||this.currentToken.getKind()==GrammarSymbols.MULTIPLICACAO||this.currentToken.getKind()==GrammarSymbols.DIVISAO){
						acceptIt();
						parseExpressao();
					}
					
				else if(this.currentToken.getKind()==GrammarSymbols.MAIOR_OU_IGUAL||this.currentToken.getKind()==GrammarSymbols.MAIOR_QUE
						||this.currentToken.getKind()==GrammarSymbols.MENOR_QUE||this.currentToken.getKind()==GrammarSymbols.MENOR_OU_IGUAL
						||this.currentToken.getKind()==GrammarSymbols.IGUAL_A||this.currentToken.getKind()==GrammarSymbols.DIFERENTE){
							acceptIt();
							parseExpressao();
					}
			}
			else if(this.currentToken.getKind()==GrammarSymbols.ID){
				acceptIt();
				if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ ){
					parseChamadaFuncao();
				}
			}
			else parseExpressao();
			}
		}
	}
	
	
	private void parseComandoWhile()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.WHILE){
			
			acceptIt();
			accept(GrammarSymbols.PARENTESE_ESQ);
			parseExpressao();
			
			while(this.currentToken.getKind()!=GrammarSymbols.END){
				
			if(this.currentToken.getKind()==GrammarSymbols.CONTINUE){
					acceptIt();
			}
			else if(this.currentToken.getKind()==GrammarSymbols.EXIT)
				acceptIt();
			else parseComandoSimples();
			}
		}
	}
		
	
	 private void parseComando()throws LexicalException,SyntacticException{
		 if(this.currentToken.getKind()==GrammarSymbols.FUNCTION){
			 parseComandoFuncao();
		 }
		 
		 else if(this.currentToken.getKind()==GrammarSymbols.SUBROUTINE){
			 parseComandoSubrotina();
		 }
		 
		 else parseComandoSimples();
	 }
	 private void parseParametros()throws LexicalException,SyntacticException{
         
         while(this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
                 
         if(this.currentToken.getKind()==GrammarSymbols.ID){
                 
                 acceptIt();
                 if(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
                         acceptIt();
                 if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_DIR){
                	 
                	 throw new SyntacticException("Erro sintatico", this.currentToken);
                 }
                 }
         }
         else if(this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO || this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL){
                 
                         acceptIt();
                         if(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
                                 acceptIt();
                         
                         if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_DIR){
                        	 
                        	 throw new SyntacticException("Erro sintatico", this.currentToken);
                         }
                         }
         }
         else {
        	 	parseExpressao();
        	 	 if(this.currentToken.getKind()==GrammarSymbols.VIRGULA){
                     acceptIt();
        	 
        	 	if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_DIR){
               	 
               	 throw new SyntacticException("Erro sintatico", this.currentToken);
                }
        	 	 }
         }
         }
 }    
	 
	private void parseComandoSubrotina()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.SUBROUTINE){
			acceptIt();
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.PARENTESE_ESQ);
			
			if(this.currentToken.getKind()!=GrammarSymbols.PARENTESE_DIR){
				
				parseParametros();
			}
			
			accept(GrammarSymbols.PARENTESE_DIR);
			
			while(this.currentToken.getKind()!=GrammarSymbols.END){
				parseComandoSimples();
			}
			
			acceptIt();
			accept(GrammarSymbols.SUBROUTINE);
		}
	}
	
	private void parseComandoFuncao()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.FUNCTION){
			acceptIt();
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.PARENTESE_ESQ);
			parseParametros();
			
			while(this.currentToken.getKind()!=GrammarSymbols.END){
				
				parseComandoSimples();
			}
			acceptIt();
			accept(GrammarSymbols.FUNCTION);
		}
	}
	private void parseChamadaFuncao() throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
			acceptIt();
			parseParametros();
			accept(GrammarSymbols.PARENTESE_DIR);	
		}
	}
	
	
	
	private void parseChamadaSubrotina()throws LexicalException,SyntacticException{
		
		if(this.currentToken.getKind()==GrammarSymbols.CALL){
			acceptIt();
			accept(GrammarSymbols.ID);
			accept(GrammarSymbols.PARENTESE_ESQ);
			parseParametros();
			accept(GrammarSymbols.PARENTESE_DIR);
		}
	}
	
	private void parseExpressao()throws SyntacticException,LexicalException{
		
		if(this.currentToken.getKind()==GrammarSymbols.PARENTESE_ESQ){
			acceptIt();
			parseExpressao();
			accept(GrammarSymbols.PARENTESE_DIR);
			if(this.currentToken.getKind()!=GrammarSymbols.EOL){
				switch(this.currentToken.getKind()){
				case GrammarSymbols.SOMA:
				case GrammarSymbols.SUBTRACAO:
				case GrammarSymbols.DIVISAO:
				case GrammarSymbols.MULTIPLICACAO:
				case GrammarSymbols.IGUAL_A:
				case GrammarSymbols.DIFERENTE:
				case GrammarSymbols.MAIOR_QUE:
				case GrammarSymbols.MAIOR_OU_IGUAL:
				case GrammarSymbols.MENOR_QUE:
					acceptIt();
					break;
				default:
					accept(GrammarSymbols.MENOR_OU_IGUAL);
					break;
		}
			parseExpressao();
			
		}
		else if(this.currentToken.getKind()==GrammarSymbols.ID || this.currentToken.getKind()==GrammarSymbols.NUMERO_INTEIRO 
				|| this.currentToken.getKind()==GrammarSymbols.NUMERO_REAL||this.currentToken.getKind()==GrammarSymbols.VERDADEIRO
				||this.currentToken.getKind()==GrammarSymbols.FALSO){
			acceptIt();
			switch(this.currentToken.getKind()){
			case GrammarSymbols.SOMA:
			case GrammarSymbols.SUBTRACAO:
			case GrammarSymbols.DIVISAO:
			case GrammarSymbols.MULTIPLICACAO:
			case GrammarSymbols.IGUAL_A:
			case GrammarSymbols.DIFERENTE:
			case GrammarSymbols.MAIOR_QUE:
			case GrammarSymbols.MAIOR_OU_IGUAL:
			case GrammarSymbols.MENOR_QUE:
				acceptIt();
				break;
			default:
				accept(GrammarSymbols.MENOR_OU_IGUAL);
				break;
		}
			parseExpressao();
			
		}
		
		else throw new SyntacticException("Erro Sintatico",this.currentToken);
		}
	}

	/**
	 * Veririfes if the current token kind is the expected one
	 * @param kind
	 * @throws SyntacticException
	 * @throws LexicalException 
	 */ //TODO
	private void accept(int kind) throws LexicalException,SyntacticException{
		// If the current token kind is equal to the expected
			// Gets next token
		// If not
			// Raises an exception
		
		if(kind == currentToken.getKind()){
	
			currentToken = scanner.getNextToken();
		}
		
		else throw new SyntacticException("Erro Sintatico", this.currentToken);
	}
	
	/**
	 * Gets next token
	 */ //TODO
	private void acceptIt() throws LexicalException {
		currentToken = scanner.getNextToken();
	}

	/**
	 * Verifies if the source program is syntactically correct
	 * @throws SyntacticException
	 */ //TODO
	public AST parse() throws SyntacticException {
		return null;
	}
	
}
